package com.security.sm;

import java.io.IOException;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import cn.hutool.core.util.HexUtil;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.digests.ShortenedDigest;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
import java.util.Base64;
/**
 *   SM2的加解密工具类
 */
public class SM2Util {
    private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
    private static ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
    private static ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
    /** 素数p */
    private static final BigInteger p = new BigInteger("FFFFFFFE" + "FFFFFFFF"
            + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "00000000" + "FFFFFFFF"
            + "FFFFFFFF", 16);

    /** 系数a */
    private static final BigInteger a = new BigInteger("FFFFFFFE" + "FFFFFFFF"
            + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "00000000" + "FFFFFFFF"
            + "FFFFFFFC", 16);

    /** 系数b */
    private static final BigInteger b = new BigInteger("28E9FA9E" + "9D9F5E34"
            + "4D5A9E4B" + "CF6509A7" + "F39789F5" + "15AB8F92" + "DDBCBD41"
            + "4D940E93", 16);

    /** 坐标x */
    private static final BigInteger xg = new BigInteger("32C4AE2C" + "1F198119"
            + "5F990446" + "6A39C994" + "8FE30BBF" + "F2660BE1" + "715A4589"
            + "334C74C7", 16);

    /** 坐标y */
    private static final BigInteger yg = new BigInteger("BC3736A2" + "F4F6779C"
            + "59BDCEE3" + "6B692153" + "D0A9877C" + "C62A4740" + "02DF32E5"
            + "2139F0A0", 16);

    /** 基点G, G=(xg,yg),其介记为n */
    private static final BigInteger n = new BigInteger("FFFFFFFE" + "FFFFFFFF"
            + "FFFFFFFF" + "FFFFFFFF" + "7203DF6B" + "21C6052B" + "53BBF409"
            + "39D54123", 16);
    public static SecureRandom random = new SecureRandom();
    public static ECDomainParameters ecc_bc_spec;
    public static ECCurve.Fp curve;
    public static ECPoint G;
    public static boolean debug = false;

    public SM2Util() {

    }
    static{
        curve = new ECCurve.Fp(p, // q
                a, // a
                b); // b
        G = curve.createPoint(xg, yg);
        ecc_bc_spec = new ECDomainParameters(curve, G, n);
    }

    /**
     * 公钥校验
     * @param publicKey 公钥
     * @return boolean true或false
     */
    private static boolean checkPublicKey(ECPoint publicKey) {
        if (!publicKey.isInfinity()) {
            BigInteger x = publicKey.getXCoord().toBigInteger();
            BigInteger y = publicKey.getYCoord().toBigInteger();
            if (between(x, new BigInteger("0"), p) && between(y, new BigInteger("0"), p)) {
                BigInteger xResult = x.pow(3).add(a.multiply(x)).add(b).mod(p);
                System.out.println("xResult: " + xResult.toString());
                BigInteger yResult = y.pow(2).mod(p);
                System.out.println("yResult: " + yResult.toString());
                if (yResult.equals(xResult) && publicKey.multiply(n).isInfinity()) {
                    return true;
                }
            }
            return false;
        } else {
            return false;
        }
    }
    /**
     * 获得公私钥对
     * @return
     */
    public static SM2KeyPair generateKeyPair() {

        //生成密钥对
        ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
        try {
            keyPairGenerator.init(new ECKeyGenerationParameters(ecc_bc_spec, SecureRandom.getInstance("SHA1PRNG")));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();

        //私钥，16进制格式，自己保存，格式如a2081b5b81fbea0b6b973a3ab6dbbbc65b1164488bf22d8ae2ff0b8260f64853
        BigInteger privatekey = ((ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate()).getD();
        //公钥，16进制格式，发给前端，格式如04813d4d97ad31bd9d18d785f337f683233099d5abed09cb397152d50ac28cc0ba43711960e811d90453db5f5a9518d660858a8d0c57e359a8bf83427760ebcbba
        ECPoint ecPoint = ((ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic()).getQ();
        String privateKeyHex = privatekey.toString(16);
        String publicKeyHex = Hex.toHexString(ecPoint.getEncoded(false));
        SM2KeyPair keyPair = new SM2KeyPair(publicKeyHex, privateKeyHex);


        if (checkPublicKey(ecPoint)) {
            System.out.println("generate key successfully");
            return keyPair;
        } else {
            System.err.println("generate key failed");
            return null;
        }
    }
    /**
     * 加密
     * @param input 待加密消息M
     * @param publicKeyStr 公钥字符串
     * @return byte[] 加密后的字节数组
     */
    public static String encrypt(String input, String publicKeyStr) {
        String encBase64Str = null;
        try {
            byte[] buffer = Hex.decode(publicKeyStr);
            ECPoint ecPointPub = x9ECParameters.getCurve().decodePoint(buffer);
            ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(ecPointPub,ecDomainParameters);
            byte[] plaintextBytes = input.getBytes();
            ZSM2Engine sm2EngineEnc = new ZSM2Engine();
            sm2EngineEnc.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));

            byte[] encBytes = null;
            encBytes = sm2EngineEnc.processBlock(plaintextBytes, 0, plaintextBytes.length);

            encBase64Str = new String(Base64.getEncoder().encode(encBytes));
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
        return encBase64Str;
    }

    public static String decrypt(String encryptData, String privateKeyStr) {
        String result = null;
        try {
            byte[] encryptByte = Base64.getDecoder().decode(encryptData);
            result = decrypt(encryptByte,privateKeyStr);
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }

    public static String decrypt(byte[] encryptData, String privateKeyStr) {
        String data = null;
        try {
            BigInteger privateKeyD = new BigInteger(privateKeyStr, 16);
            ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, ecDomainParameters);
            //解密-------------------------------------------------------------------------------------------------------

            //用私钥解密
            ZSM2Engine sm2EngineDec = new ZSM2Engine();
            sm2EngineDec.init(false, privateKeyParameters);
            byte[] arrayOfBytes = null;
            try {
                arrayOfBytes = sm2EngineDec.processBlock(encryptData, 0, encryptData.length);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InvalidCipherTextException e) {
                e.printStackTrace();
            }
            //得到明文：SM2 Encryption Test
            data = new String(arrayOfBytes);
            return data;
        }catch (Exception e){
            return data;
        }
    }

    private static byte[] calculateHash(BigInteger x2, byte[] M, BigInteger y2) {
        ShortenedDigest digest = new ShortenedDigest(new SHA256Digest(), 20);
        byte[] buf = x2.toByteArray();
        digest.update(buf, 0, buf.length);
        digest.update(M, 0, M.length);
        buf = y2.toByteArray();
        digest.update(buf, 0, buf.length);

        buf = new byte[20];
        digest.doFinal(buf, 0);
        return buf;
    }

    private static boolean between(BigInteger param, BigInteger min, BigInteger max) {
        if (param.compareTo(min) >= 0 && param.compareTo(max) < 0) {
            return true;
        } else {
            return false;
        }
    }
    public static String byteArrayToHexString(byte[] a) {
        StringBuilder sb = new StringBuilder(a.length * 2);
        for (byte b : a) {
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }

    /** 元消息串 */
    private static String M = "{\"sessionId\":\"123456789\",\"customerNo\":\"12345678\",\"requestContent\":\"你好\",\"requestType\":\"text\"}";

    public static void main(String[] args) {
        SM2KeyPair keyPair = SM2Util.generateKeyPair();
        String publicKeyNativeFull = keyPair.getPublicKeyStr();
        System.out.println(publicKeyNativeFull);
        String pukBase64 = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE8L7UVUVj19iR8UkOs7ZbHGWxAg05o+S2rt0BnqKVh/GJFSr56xuGFRdBofFaG5DXs7WfljFvm+mcyWS1SQ2Ymw==";
        String prkBase64 = "MHcCAQEEIGbF7pbN82dk08jk4nSTjcGK1yWXRg9aeReLq9GQhXoRoAoGCCqBHM9VAYItoUQDQgAE8L7UVUVj19iR8UkOs7ZbHGWxAg05o+S2rt0BnqKVh/GJFSr56xuGFRdBofFaG5DXs7WfljFvm+mcyWS1SQ2Ymw==";

        publicKeyNativeFull = HexUtil.encodeHexStr(Base64.getDecoder().decode(pukBase64));

        System.out.println("publicKeyNativeFull:" + publicKeyNativeFull);
        String privateKeyNativeFull =  keyPair.getPrivateKeyStr();
        System.out.println(privateKeyNativeFull);
        privateKeyNativeFull = HexUtil.encodeHexStr(Base64.getDecoder().decode(prkBase64));
        System.out.println("privateKeyNativeFull:" + privateKeyNativeFull);

        String test  = SM2Util.encrypt(M,publicKeyNativeFull);
        System.out.println("data is:"+test);
        String resultData1 = SM2Util.decrypt(test,privateKeyNativeFull);
        System.out.println("resultData:" + resultData1);
    }
}
